﻿using BusinessObjects;
using YourWorld.Common.Infrastucture.Events;
using System.Xml.Serialization;
using System.IO;
using System.Net.Sockets;
using System;
using System.Text;
using System.Windows.Threading;
using System.Net;
using System.Windows;
using System.Collections.Generic;

namespace YourWorld.Common.Services
{
    public class SocketCommunicator
    {
        private static volatile object sync = new Object();

        private const int bufferSize = 10240 * 5;
        private bool isConnected = false;
        private Socket socket;
        private List<byte> messageBuffer;

        public Action<Player> SocketConnected;

        public Action<byte[]> PlayerSubscribedCompleted;
        public Action<byte[]> PlayerUnsubscribedCompleted;
        public Action<Game> GameInitializationCompleted;
        public Action<byte[]> MoveToPositionCompleted;
        public Action<byte[]> BinsUpdateCompleted;
        public Action<byte[]> SupportEventRequestReceived;
        public Action<byte[]> JoinEventRequestReceived;
        public Action<byte[]> ChatMessageReceived;
        public Action<byte[]> RubbishUpdateReceived;
        public Action<byte[]> PlayerConnectionRequestReceived;
        public Action<byte[]> PlayerConnectionResponseReceived;
        public Action<byte[]> JoinEventResponseReceived;
        public Action<byte[]> SupportEventResponseReceived;

        public Action<byte[]> PublishBuildQuestResponseReceived;
        public Action<byte[]> BuildQuestUpdateRequestReceived;
        public Action<byte[]> BuildTeamQuestUpdateRequestReceived;

        public Action<byte[]> PublishBadgeWonReceived;
        public Action<byte[]> QuestCompletedEventReceived;

        public Action<Exception> ServerCommunicationFailed;
        public Action RequestConnectionRetry;

        private Socket messanger;
        private DnsEndPoint endPoint;

        public SocketCommunicator()
        {
            this.messageBuffer = new List<byte>();
        }

        public Player Player
        {
            get;
            set;
        }

        public void Connect( Player player )
        {
            lock ( sync )
            {
                if ( this.isConnected == true )
                    return;

                if ( InitializeServerManager.IsInitialized == false &&
                    InitializeServerManager.IsInicializationRequested == true )
                {
                    Deployment.Current.Dispatcher.BeginInvoke( () => this.OnRequestConnectionRetry() );
                    return;
                }

                try
                {
                    this.Player = player;

                    this.endPoint = new DnsEndPoint( Application.Current.Host.Source.DnsSafeHost, 4531 );

                    this.socket = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp );
                    this.messanger = socket;

                    this.messanger.SendBufferSize = 1024;
                    //  this.messanger.ReceiveBufferSize = 10240;

                    SocketAsyncEventArgs args = new SocketAsyncEventArgs();
                    args.UserToken = socket;
                    args.RemoteEndPoint = endPoint;
                    args.Completed += new EventHandler<SocketAsyncEventArgs>( OnSocketConnected );

                    this.socket.ConnectAsync( args );
                }
                catch ( Exception e )
                {
                    if ( this.socket != null )
                        this.socket.Close();

                    Deployment.Current.Dispatcher.BeginInvoke( () => this.OnServerCommunicationFailed( e ) );
                }
            }
        }

        public void SendMessage( byte[] msg )
        {
            SocketAsyncEventArgs args = new SocketAsyncEventArgs();
            args.UserToken = this.messanger;
            args.RemoteEndPoint = this.endPoint;
            args.SetBuffer( msg, 0, msg.Length );

            args.Completed += new EventHandler<SocketAsyncEventArgs>( OnSocketMessageSent );

            this.messanger.SendAsync( args );

        }

        public void Disconnect()
        {
            this.isConnected = false;
        }

        private void OnSocketConnected( object sender, SocketAsyncEventArgs e )
        {
            try
            {
                if ( this.isConnected == true )
                    return;

                if ( e.SocketError == SocketError.Success )
                {
                    this.isConnected = true;

                    byte[] response = new byte[bufferSize];
                    e.SetBuffer( response, 0, response.Length );
                    e.Completed -= new EventHandler<SocketAsyncEventArgs>( OnSocketConnected );
                    e.Completed += new EventHandler<SocketAsyncEventArgs>( OnSocketStartReceive );

                    Socket socket = ( Socket )e.UserToken;
                    socket.ReceiveAsync( e );

                    if ( this.SocketConnected != null )
                        this.SocketConnected( this.Player );
                }
                else
                {
                    if ( e.SocketError != SocketError.AccessDenied )
                        Deployment.Current.Dispatcher.BeginInvoke( () => this.OnServerCommunicationFailed( new Exception( e.SocketError.ToString() ) ) );
                    else
                        Deployment.Current.Dispatcher.BeginInvoke( () => this.OnRequestConnectionRetry() );
                }
            }
            catch ( Exception ex )
            {
                if ( this.socket != null )
                    this.socket.Close();

                Deployment.Current.Dispatcher.BeginInvoke( () => this.OnServerCommunicationFailed( ex ) );
            }
        }

        private void OnSocketStartReceive( object sender, SocketAsyncEventArgs e )
        {
            try
            {
                int i, start = 0, end = e.BytesTransferred;
                bool isComplete = false;

                if ( e.SocketError != SocketError.Success )
                    throw new Exception( e.SocketError.ToString() );

                if ( e.BytesTransferred > 3 )
                {
                    if ( e.Buffer[0] == 0 && e.Buffer[1] == 0 && e.Buffer[2] == 0 )
                    {
                        // This is the beginning of a new message.
                        this.messageBuffer.Clear();
                        start = 3;
                    }

                    if ( e.Buffer[end - 1] == 0 && e.Buffer[end - 2] == 0 && e.Buffer[end - 3] == 0 )
                    {
                        // This is the end of a message.
                        isComplete = true;
                        end = e.BytesTransferred - 3;
                    }

                    for ( i = start; i < end; i++ )
                        this.messageBuffer.Add( e.Buffer[i] );

                    if ( isComplete == true )
                        this.ServerMessageReceived( this.messageBuffer.ToArray() );
                }

                Socket socket = ( Socket )e.UserToken;
                socket.ReceiveAsync( e );
            }
            catch ( Exception exception )
            {
                if ( this.socket != null )
                    this.socket.Close();

                Deployment.Current.Dispatcher.BeginInvoke( () => this.OnServerCommunicationFailed( exception ) );
            }
        }

        private void ServerMessageReceived( byte[] buffer )
        {
            if ( buffer[0] < 0 || buffer[0] >= ( byte )Consts.CountOfMessageTypes )
                return;

            byte[] message = new byte[buffer.Length - 1];

            for ( int i = 1; i < buffer.Length; i++ )
                message[i - 1] = buffer[i];

            switch ( buffer[0] )
            {
                case ( byte )SocketMessageKeys.Subscribe:
                    if ( this.PlayerSubscribedCompleted != null )
                        Deployment.Current.Dispatcher.BeginInvoke( () => this.PlayerSubscribedCompleted( message ) );
                    break;

                case ( byte )SocketMessageKeys.GameInicialization:
                    if ( this.GameInitializationCompleted != null )
                    {
                        Deployment.Current.Dispatcher.BeginInvoke( () =>
                        {
                            string xmlData = Encoding.UTF8.GetString( message, 0, message.Length );

                            XmlSerializer xmlSrl = new XmlSerializer( typeof( Game ) );
                            StringReader stream = new StringReader( xmlData );

                            Game game = xmlSrl.Deserialize( stream ) as Game;

                            this.GameInitializationCompleted( game );
                        } );
                    }
                    break;

                case ( byte )SocketMessageKeys.MoveToPosition:
                    if ( this.MoveToPositionCompleted != null )
                        Deployment.Current.Dispatcher.BeginInvoke( () => this.MoveToPositionCompleted( message ) );
                    break;

                case ( byte )SocketMessageKeys.UnSubscribe:
                    if ( this.PlayerUnsubscribedCompleted != null )
                        Deployment.Current.Dispatcher.BeginInvoke( () => this.PlayerUnsubscribedCompleted( message ) );
                    break;

                case ( byte )SocketMessageKeys.BinsStatusUpdate:
                    if ( this.BinsUpdateCompleted != null )
                        Deployment.Current.Dispatcher.BeginInvoke( () => this.BinsUpdateCompleted( message ) );
                    break;

                case ( byte )SocketMessageKeys.SupportEventRequest:
                    if ( this.SupportEventRequestReceived != null )
                        Deployment.Current.Dispatcher.BeginInvoke( () => this.SupportEventRequestReceived( message ) );
                    break;

                case ( byte )SocketMessageKeys.JoinEventRequest:
                    if ( this.JoinEventRequestReceived != null )
                        Deployment.Current.Dispatcher.BeginInvoke( () => this.JoinEventRequestReceived( message ) );
                    break;

                case ( byte )SocketMessageKeys.JoinEventResponse:
                    if ( this.JoinEventResponseReceived != null )
                        Deployment.Current.Dispatcher.BeginInvoke( () => this.JoinEventResponseReceived( message ) );
                    break;

                case ( byte )SocketMessageKeys.SupportEventResponse:
                    if ( this.SupportEventResponseReceived != null )
                        Deployment.Current.Dispatcher.BeginInvoke( () => this.SupportEventResponseReceived( message ) );
                    break;

                case ( byte )SocketMessageKeys.Chat:
                    if ( this.ChatMessageReceived != null )
                        Deployment.Current.Dispatcher.BeginInvoke( () => this.ChatMessageReceived( message ) );
                    break;

                case ( byte )SocketMessageKeys.RubbishUpdate:
                    if ( this.RubbishUpdateReceived != null )
                        Deployment.Current.Dispatcher.BeginInvoke( () => this.RubbishUpdateReceived( message ) );
                    break;

                case ( byte )SocketMessageKeys.PlayerConnectionRequest:
                    if ( this.PlayerConnectionRequestReceived != null )
                        Deployment.Current.Dispatcher.BeginInvoke( () => this.PlayerConnectionRequestReceived( message ) );
                    break;

                case ( byte )SocketMessageKeys.PlayerConnectionResponse:
                    if ( this.PlayerConnectionResponseReceived != null )
                        Deployment.Current.Dispatcher.BeginInvoke( () => this.PlayerConnectionResponseReceived( message ) );
                    break;

                case ( byte )SocketMessageKeys.BuildQuestUpdate:
                    if ( this.PublishBuildQuestResponseReceived != null )
                        Deployment.Current.Dispatcher.BeginInvoke( () => this.PublishBuildQuestResponseReceived( message ) );
                    break;

                case ( byte )SocketMessageKeys.BuildQuestUpdateRequest:
                    if ( this.BuildQuestUpdateRequestReceived != null )
                        Deployment.Current.Dispatcher.BeginInvoke( () => this.BuildQuestUpdateRequestReceived( message ) );
                    break;

                case ( byte )SocketMessageKeys.BuildTeamQuestUpdateRequest:
                    if ( this.BuildTeamQuestUpdateRequestReceived != null )
                        Deployment.Current.Dispatcher.BeginInvoke( () => this.BuildTeamQuestUpdateRequestReceived( message ) );
                    break;

                case ( byte )SocketMessageKeys.BadgeWonUpdate:
                    if ( this.PublishBadgeWonReceived != null )
                        Deployment.Current.Dispatcher.BeginInvoke( () => this.PublishBadgeWonReceived( message ) );
                    break;

                case ( byte )SocketMessageKeys.QuestCompleted:
                    if ( this.QuestCompletedEventReceived != null )
                        Deployment.Current.Dispatcher.BeginInvoke( () => this.QuestCompletedEventReceived( message ) );
                    break;

                case ( byte )SocketMessageKeys.Ping:

                    byte[] pingResponseMessage = new byte[2];
                    pingResponseMessage[0] = ( byte )SocketMessageKeys.PingResponse;
                    pingResponseMessage[1] = buffer[1];

                    this.SendMessage( pingResponseMessage );

                    break;
            }
        }

        private void OnSocketMessageSent( object sender, SocketAsyncEventArgs e )
        {
        }

        private void OnServerCommunicationFailed( Exception exception )
        {
            MessageBox.Show( String.Format( "Server communication failure, please restart the server. Error details: {0}. Error StackTrace: {1}",
                       exception.Message, exception.StackTrace ), "Error", MessageBoxButton.OK );

            if ( this.ServerCommunicationFailed != null )
                this.ServerCommunicationFailed( exception );
        }

        private void OnRequestConnectionRetry()
        {
            try
            {
                if ( InitializeServerManager.IsInitialized == false && InitializeServerManager.IsInicializationRequested == false )
                    InitializeServerManager.Instance.Initialize();
            }
            catch ( Exception e )
            {
                InitializeServerManager.IsInicializationRequested = false;
                this.OnServerCommunicationFailed( e );
            }

            if ( this.RequestConnectionRetry != null )
                this.RequestConnectionRetry();
        }

    }
}
